/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */
package sk.jp.commons.utils;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

/**
 * Utilities for date.
 * 
 * @author Jan Pastor 
 */
@SuppressWarnings( { "deprecation" })
public class DateUtils {

    /**
     * Display pattern 'yyyy-MM-dd HH:mm:ss'.
     */
    public static final String SORTABLE_DATETIME = "yyyy-MM-dd HH:mm:ss";
    /**
     * Format fot pattern 'yyyy-MM-dd HH:mm:ss'.
     */
    public static final DateFormat FORMAT_SORTABLE_DATETIME;

    /**
     * Display pattern 'yyyy-MM-dd'.
     */
    public static final String SORTABLE_DATE = "yyyy-MM-dd";
    /**
     * Format fot pattern 'yyyy-MM-dd'.
     */
    public static final DateFormat FORMAT_SORTABLE_DATE;

    /**
     * Display pattern 'HH:mm:ss'.
     */
    public static final String TIME = "HH:mm:ss";
    /**
     * Format fot pattern 'HH:mm:ss'.
     */
    public static final DateFormat FORMAT_TIME;

    /**
     * Display pattern 'd.M.yyyy'.
     */
    public static final String DATE = "d.M.yyyy";
    /**
     * Format fot pattern 'd.M.yyyy'.
     */
    public static final DateFormat FORMAT_DATE;

    /**
     * Display pattern 'd.M.yyyy, HH:mm:ss'.
     */
    public static final String DATETIME = "d.M.yyyy, HH:mm:ss";
    /**
     * Format fot pattern 'd.M.yyyy, HH:mm:ss'.
     */
    public static final DateFormat FORMAT_DATETIME;

    /**
     * Display pattern 'yyyyMMddHHmmssSSS'.
     */
    public static final String DATETIME_TIMESTAMP = "yyyyMMddHHmmssSSS";
    /**
     * Format fot pattern 'yyyyMMddHHmmssSSS'.
     */
    public static final DateFormat FORMAT_DATETIME_TIMESTAMP;

    // Initialize static date format instances:
    static {
        FORMAT_SORTABLE_DATETIME = new SimpleDateFormat(SORTABLE_DATETIME, Locale.US);
        FORMAT_SORTABLE_DATE = new SimpleDateFormat(SORTABLE_DATE, Locale.US);
        FORMAT_TIME = new SimpleDateFormat(TIME, Locale.US);
        FORMAT_DATE = new SimpleDateFormat(DATE, Locale.US);
        FORMAT_DATETIME = new SimpleDateFormat(DATETIME, Locale.US);
        FORMAT_DATETIME_TIMESTAMP = new SimpleDateFormat(DATETIME_TIMESTAMP, Locale.US);
    }

    // ----------------------------------------------------------------------

    private DateUtils() {
    }

    // ----------------------------------------------------------------------

    /**
     * Return string representation of given <code>date</code>.
     * <p>
     * Result form is dedicated by <code>pattern</code> and
     * <code>Locle.US</code>. For usable patterns see
     * <code>java.text.SimpleDateFormat</code>.
     * <p>
     * Examples:<br>
     * <b>Pattern</b> - <b>Result</b><br>
     * "<i>yyyy-MM-dd</i>" - "<i>2005-06-15</i>"
     * <p>
     * <b>Note</b>: if you not need use your own pattern, method
     * <code>date2String(Date, DateFormat)</code> is better solution (faster).
     * @param date date to transform.
     * @param pattern pattern used for date transform.
     * @return string representation of date.
     */
    public static String date2String(Date date, String pattern) {
        DateFormat df = new SimpleDateFormat(pattern, Locale.US);
        return df.format(date);
    }

    /**
     * Return string representation of given <code>date</code>.
     * <p>
     * Result form is dedicated by <code>format</code> and <code>Locle.US</code>.
     * <p>
     * See <code>FORMAT_...</code> for preddefined format instances.
     * @param date date to transform.
     * @param format date format instance used for date transform.
     * @return string representation of date.
     */
    public static String date2String(Date date, DateFormat format) {
        return format.format(date);
    }

    /**
     * Return string representation of current date.
     * <p>
     * Result form is dedicated by <code>pattern</code> and
     * <code>Locle.US</code>. For usable patterns see
     * <code>java.text.SimpleDateFormat</code>.
     * <p>
     * Examples:<br>
     * <b>Pattern</b> - <b>Result</b><br>
     * "<i>yyyy-MM-dd</i>" - "<i>2005-06-15</i>",<br>"<i>yyyy-MM-dd
     * HH:mm:ss</i>" - "<i>2005-07-25 15:51:32</i>".<br>
     * <p>
     * <b>Note</b>: if you not need use your own pattern, method
     * <code>date2String(DateFormat)</code> is better solution (faster).
     * @param pattern pattern used for date transform.
     * @return string representation of date.
     */
    public static String date2String(String pattern) {
        return date2String(new Date(), pattern);
    }

    /**
     * Return string representation of current date.
     * <p>
     * Result form is dedicated by <code>pattern</code> and
     * <code>Locle.US</code>.
     * <p>
     * See <code>FORMAT_...</code> for preddefined format instances.
     * @param format date format instance used for date transform.
     * @return string representation of date.
     */
    public static String date2String(DateFormat format) {
        return date2String(new Date(), format);
    }

    /**
     * Returns current date formated using given <code>pattern</code> and
     * <code>locale</code>.
     * @param pattern pattern for transform current date. See
     *            {@link SimpleDateFormat}.
     * @param locale locale.
     * @return current date in string format.
     */
    public static String date2String(String pattern, Locale locale) {
        DateFormat df = new SimpleDateFormat(pattern, locale);
        return df.format(new Date());
    }

    /**
     * Returns <code>Date</code> instance, created from given
     * <code>string</code>.
     * <p>
     * Given <code>string</code> is parsed with using given <code>pattern</code>.
     * <p>
     * <b>Note</b>: if you not need use your own pattern, method
     * <code>string2date(String, DateFormat)</code> is better solution (faster).
     * @param string string that contains date in form specified in
     *            <code>pattern</code>.
     * @param pattern specifies form of <code>string</code>.
     * @return instance of <code>Date</code>.
     * @throws ParseException if the beginning of the specified string cannot be
     *             parsed.
     */
    public static Date string2date(String string, String pattern) throws ParseException {
        DateFormat df = new SimpleDateFormat(pattern, Locale.US);
        df.setLenient(false);
        return df.parse(string);
    }

    /**
     * Returns <code>Date</code> instance, created from given
     * <code>string</code>.
     * <p>
     * Given <code>string</code> is parsed with using given <code>pattern</code>.
     * @param string string that contains date in form specified in
     *            <code>pattern</code>.
     * @param format date format instance used for date transform.
     * @return instance of <code>Date</code>.
     * @throws ParseException if the beginning of the specified string cannot be
     *             parsed.
     */
    public static Date string2date(String string, DateFormat format) throws ParseException {
        format.setLenient(false);
        return format.parse(string);
    }

    /**
     * Tests if current hour is in specified interval.
     * <p>
     * Example 1:<br>
     * If startHour is 7 and endHour is 16, returns true, if current hour is
     * between 7:00:00,000 (inclusive) and 15:59:59,999 (inclusive).
     * <p>
     * Example 2:<br>
     * If startHour is 19 and endHour is 6, returns true, if current hour is
     * between 19:00:00,000 (inclusive) and (next day) 5:59:59,999 (inclusive).
     * <p>
     * Example 3:<br>
     * If startHour is 13 and endHour is 13 (startHour equalt to endHour),
     * returns allways true.
     * @param startHour (0 - 23).
     * @param endHour (0 - 23).
     * @return true, if current hour of day is in specified interval; false in
     *         other case.
     * @throws IllegalArgumentException if given hours are out of specified
     *             range.
     */
    public static boolean isInHours(int startHour, int endHour) {
        if (startHour < 0 || 23 < startHour) {
            throw new IllegalArgumentException("Invalid value of startHour (" + startHour + ")");
        }
        if (endHour < 0 || 23 < endHour) {
            throw new IllegalArgumentException("Invalid value of endHour (" + endHour + ")");
        }
        //
        int currentHour = (new Date()).getHours();
        if (startHour < endHour) {
            if (startHour <= currentHour && currentHour < endHour) {
                return true;
            }
        } else if (endHour < startHour) {
            if (startHour <= currentHour && currentHour < 24) {
                return true;
            }
            if (0 <= currentHour && currentHour < endHour) {
                return true;
            }
        } else { // if startHour == endHour, always true.
            return true;
        }
        return false;
    }

    /**
     * Parses given pseudodate to two dates (from and to).
     * <p>
     * @param pseudodate date in form YYYYMMDD, where absent year has value
     *            0000, absent month or day has value 00.
     * @return array of two Dates, where first is from date and second to date.
     *         If year of pseudodate is absent, year of result dates are 1 and
     *         9999. If month of pseudodate is absent, months in result dates
     *         are 0 and 11. If day in pseudodate is absent, day in result dates
     *         are 0 and max_for_month.
     * @throws IllegalArgumentException if given pseudodate length is not 8
     *             characters, or if contains illegal characters.
     */
    public static Date[] parsePseudodate(String pseudodate) throws IllegalArgumentException {
        if (pseudodate == null)
            throw new NullPointerException("Parameter pseudodate is null");
        if (pseudodate.length() != 8 || Long.parseLong(pseudodate) < 0) {
            throw new IllegalArgumentException("Unsupported date ('" + pseudodate + "')");
        }
        //
        String year = pseudodate.substring(0, 4);
        String month = pseudodate.substring(4, 6);
        String day = pseudodate.substring(6, 8);

        int yearFrom;
        int yearTo;
        if ("0000".equals(year)) {
            yearFrom = 1;
            yearTo = 9999;
        } else {
            yearFrom = Integer.parseInt(year);
            yearTo = yearFrom;
        }
        int monthFrom;
        int monthTo;
        if ("00".equals(month)) {
            monthFrom = 0;
            monthTo = 11;
        } else {
            monthFrom = Integer.parseInt(month) - 1;
            monthTo = monthFrom;
        }
        int dayFrom;
        int dayTo;
        if ("00".equals(day)) {
            dayFrom = 1;
            GregorianCalendar cal = new GregorianCalendar(yearTo, monthTo, 1);
            dayTo = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        } else {
            dayFrom = Integer.parseInt(day);
            dayTo = dayFrom;
        }

        return new Date[] { new Date(yearFrom - 1900, monthFrom, dayFrom),
                new Date(yearTo - 1900, monthTo, dayTo) };
    }

    /**
     * Create pseudodate in format YYYYMMDD from dates range.
     * <p>
     * If from and to date equals, returns concrete date. If years not equals,
     * result pseudodate has year 0000. If month or day not equals, result
     * pseudodate has month 00.
     * @param from from date.
     * @param to to date.
     * @return date in format YYYYMMDD, where absent year has value 0000, absent
     *         month or day has value 00.
     * @throws IllegalArgumentException if from date is greater to to date.
     */
    public static String createPseudodate(Date from, Date to) throws IllegalArgumentException {
        if (from == null)
            throw new NullPointerException("Parameter from is null");
        if (to == null)
            throw new NullPointerException("Parameter to is null");
        if (from.compareTo(to) > 0) {
            throw new IllegalArgumentException("Date to is greater than date from");
        }
        //
        String year;
        if (from.getYear() != to.getYear()) {
            year = "0000";
        } else {
            year = StringUtils.fillString(Integer.toString(from.getYear() + 1900), '0', 4);
        }
        String month;
        if (from.getMonth() != to.getMonth()) {
            month = "00";
        } else {
            month = StringUtils.fillString(Integer.toString(from.getMonth() + 1), '0', 2);
        }
        String day;
        if (from.getDate() != to.getDate()) {
            day = "00";
        } else {
            day = StringUtils.fillString(Integer.toString(from.getDate()), '0', 2);
        }
        return year + month + day;
    }

    /**
     * Make new date instance, computed from given date and days offset.
     * <p>
     * New date is set from near next midnight and days offset. If source date
     * is on midnight, new date is source date + offset days.
     * <p>
     * E.g. if date is 1.2.2006 15:56:23 and offset days is 3, result is
     * 5.2.2006 00:00:00. If date is 23.4.2007 00:00:00 and offset is 2, result
     * is 25.4.2007 00:00:00.
     * @param date source date.
     * @param days offset in days.
     * @return new date instance.
     */
    public static Date dateOffset(Date date, int days) {
        if (date == null)
            throw new NullPointerException("Parameter date is null");
        if (days < 0)
            throw new IllegalArgumentException("Parameter days has illegal value (" + days + ")");
        Date out = new Date(date.getYear(), date.getMonth(), date.getDate() + days, 0, 0, 0);
        return out;
    }

    /**
     * Returns name of month of given date.
     * @param date date.
     * @param locale used for language.
     * @return month name (e.g. 'October').
     */
    public static String getNameOfMonth(Date date, Locale locale) {
        DateFormat df = new SimpleDateFormat("MMMMM", locale);
        return df.format(date);
    }

    /**
     * Returns number of month of given date.
     * @param date date.
     * @return month number (with dot) (e.g. '12.').
     */
    public static String getNumberOfMonth(Date date) {
        return (date.getMonth() + 1) + ".";
    }

    /**
     * Returns name of day of week of given date.
     * @param date date.
     * @param locale used for language.
     * @return month name (e.g. 'Monday').
     */
    public static String getNameOfDay(Date date, Locale locale) {
        DateFormat df = new SimpleDateFormat("EEEEE", locale);
        return df.format(date);
    }

    /**
     * Returns day of month of given date.
     * @param date date.
     * @return number of day of month (e.g. '24.').
     */
    public static String getDayOfMonth(Date date) {
        return date.getDate() + ".";
    }

    /**
     * Returns year for given date instance.
     * @param date date (mandatory parameter, if null IllegalArgumentException
     *            is rised)
     * @return year (e.g. 1983)
     */
    public static int getYear(Date date) {
        if (date == null)
            throw new IllegalArgumentException("Parameter 'date' must not be 'null'");
        Calendar calendar = Calendar.getInstance();
        calendar.setLenient(false);
        calendar.setTime(date);
        return calendar.get(Calendar.YEAR);
    }

    /**
     * Returns maximum from given dates. 
     * @param date1
     * @param date2
     * @return date1 < date2 ? date2 : date1
     */
    public static Date max(Date date1, Date date2) {
        if (date1 == null)
            throw new IllegalArgumentException("Parameter 'date1' must not be 'null'");
        if (date2 == null)
            throw new IllegalArgumentException("Parameter 'date2' must not be 'null'");
        
        return date1.getTime() < date2.getTime() ? date2 : date1;
    }
    
    /**
     * Returns minimum from given dates. 
     * @param date1
     * @param date2
     * @return date1 < date2 ? date1 : date2
     */
    public static Date min(Date date1, Date date2) {
        if (date1 == null)
            throw new IllegalArgumentException("Parameter 'date1' must not be 'null'");
        if (date2 == null)
            throw new IllegalArgumentException("Parameter 'date2' must not be 'null'");
        
        return date1.getTime() < date2.getTime() ? date1 : date2;
    }

}
